/**
 * Copyright (c) 2010-2011 Pierre Labatut
 *
 * This file is part of Space-Chronicles project.
 *
 * Space-Chronicles is free software: you can redistribute it and/or modify it
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or <http://code.google.com/p/space-chronicles/>.
 */
package space.app.skyview;

import java.io.IOException;
import java.net.URL;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.opengl.GL;
import javax.media.opengl.GL3bc;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import jinngine.math.Matrix4;
import jinngine.math.Quaternion;
import jinngine.math.Vector3;
import space.app.skyview.TextureMessage.Kind;
import space.debug.DbgError;
import space.game.world.WldSkyBox;
import space.model.RenderingCamera;

/**
 *
 * @author Pierre
 */
class SkyEventListner implements GLEventListener {

    private WldSkyBox skybox;
    private boolean drawBox;
    private boolean headup;
    private double alpha, beta;
    private final Matrix4 complete = new Matrix4(
            1, 0, 0, 0,
            0, 0, 1, 0,
            0, -1, 0, 0,
            0, 0, 0, 1);
    private final MouseCamera mouseCamera;
    private final RenderingCamera camera;
    private final URL boxfile;

    private GL3bc getGL(GLAutoDrawable drawable) {
        return (GL3bc) drawable.getGL();
    }

    interface GLRunnable {

        void run(GL gl);
    }
    private final Queue<GLRunnable> operations =
            new ConcurrentLinkedQueue<GLRunnable>();

    /**
     *
     * @param drawBox should the debug box be drawn
     * @param fovy degree
     * @param boxfile null means defautl box texture
     */
    public SkyEventListner(boolean drawBox, float fovy, boolean headup, URL boxfile) {
        this.drawBox = drawBox;
        this.mouseCamera = new MouseCamera(fovy);
        this.camera = new RenderingCamera(mouseCamera);
        this.headup = headup;
        this.boxfile = boxfile;
    }

    public void updateFov(final float fov) {
        operations.add(new GLRunnable() {

            public void run(GL gl) {
                mouseCamera.setFov(fov);
            }
        });
    }

    public void updateDrawBox(final boolean db) {
        operations.add(new GLRunnable() {

            public void run(GL gl) {
                drawBox = db;
            }
        });
    }

    void load(final URL url) {
        operations.add(new GLRunnable() {

            public void run(GL gl) {
                skybox.load(url);
            }
        });
    }

    void updateHeadUp(final boolean selected) {
        operations.add(new GLRunnable() {

            public void run(GL gl) {
                headup = selected;
                complete.assign(
                        1, 0, 0, 0,
                        0, 0, 1, 0,
                        0, -1, 0, 0,
                        0, 0, 0, 1);
                alpha = 0;
                beta = 0;
            }
        });
    }

    public void updateTexture(final Kind kind, final byte[] texture) {
        operations.add(new GLRunnable() {

            public void run(GL gl) {
                skybox.updateTexture(gl, kind, texture);
            }
        });
    }

    public void init(final GLAutoDrawable drawable) {
        try {
            if (boxfile != null) {
                skybox = new WldSkyBox(boxfile);
            } else {
                skybox = new WldSkyBox();
            }
        } catch (final IOException ex) {
            Logger.getLogger(SkyEventListner.class.getName()).log(Level.SEVERE, null,
                    ex);
        }
        final GL3bc gl = getGL(drawable);
        gl.glClearColor(0.1f, 0.1f, 0.1f, 1.f);
        gl.setSwapInterval(1); //enable vertical synchronisation
    }

    public void dispose(GLAutoDrawable drawable) {
    }

    public void display(GLAutoDrawable drawable) {
         final GL3bc gl = getGL(drawable);

        for (GLRunnable op = operations.poll(); op != null; op = operations.poll()) {
            op.run(gl);
        }


        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);

        /**
         * Setup viewpoint
         */
        {
            Matrix4 perspective = camera.perspective(drawable.getWidth(),
                    drawable.getHeight());
            gl.glMatrixMode(GL3bc.GL_PROJECTION);
            gl.glLoadMatrixd(perspective.toArray(), 0);
        }

        gl.glMatrixMode(GL3bc.GL_MODELVIEW);
        gl.glLoadMatrixd(complete.toArray(), 0);
        
        skybox.draw(gl);
        if (drawBox) {
            gl.glColor3f(1, 1, 1);
            gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE);
            skybox.drawDebug(gl);
        }

    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        final GL3bc gl = (GL3bc) drawable.getGL();
        gl.glViewport(0, 0, width, height);
        gl.glScissor(0, 0, width, height);
    }

    public void commitView(final int i, final int i0) {
        operations.add(new GLRunnable() {

            public void run(GL gl) {
                if (headup == false) {
                    final double factor = .005;
                    final Quaternion q1 = Quaternion.rotation(i0 * factor, new Vector3(
                            -1,
                            0, 0));
                    final Quaternion q2 = Quaternion.rotation(i * factor, new Vector3(
                            0,
                            -1, 0));
                    Matrix4 currentDrag = new Matrix4();
                    q1.multiply(q2).toMatrix4(currentDrag);
                    Matrix4.multiply(currentDrag, complete, complete);
                } else {
                    alpha += i * .01;
                    beta += i0 * .01;
                    beta = beta > Math.PI * .5 ? Math.PI * .5 : beta;
                    beta = beta < -Math.PI * .5 ? -Math.PI * .5 : beta;
                    complete.assign(
                            1, 0, 0, 0,
                            0, 0, 1, 0,
                            0, -1, 0, 0,
                            0, 0, 0, 1);
                    double c, s;
                    c = Math.cos(beta);
                    s = Math.sin(beta);
                    Matrix4.multiply(complete,
                            new Matrix4(
                            1, 0, 0, 0,
                            0, c, -s, 0,
                            0, s, c, 0,
                            0, 0, 0, 1),
                            complete);
                    c = Math.cos(alpha);
                    s = Math.sin(alpha);
                    Matrix4.multiply(complete,
                            new Matrix4(
                            c, -s, 0, 0,
                            s, c, 0, 0,
                            0, 0, 1, 0,
                            0, 0, 0, 1),
                            complete);

                }
            }
        });

    }
}
